import datetime
import json
import os

import Logger
import Util
from ExcelData import ExcelData, TableData, FieldData, FieldType, EData, LoadConfig, IndexType, Platform, Union
from typing import List, Dict

from ICodeGenerator import ICodeGenerator


class CodeJavaGenerator(ICodeGenerator):
    CODE_NAME: str = "Java"
    CODE_FILE_EXT_NAME: str = ".java"

    TEMPLATE_VO_FILEPATH: str = "template\\JavaVoClass.template"
    TEMPLATE_EASY_ACCESS_FILEPATH: str = "template\\JavaEasyAccessClass.template"
    TEMPLATE_LIB_CONSTANT: str = "template\\JavaLibConstantClass.template"

    VO_FIELD_COMMENT: str = """    
    /**
    * [%Comment%]
    **/"""
    VO_FIELD_DEFINE: str = """    private [%FieldType%] [%FieldName%];"""
    VO_FIELD_GET: str = """    public [%FieldType%] [%Get%]() { return [%FieldName%]; }"""
    VO_FIELD_SET: str = """    public void [%Set%]([%FieldType%] [%FieldName%]) { this.[%FieldName%] = [%FieldName%]; }"""
    VO_TOSTRING: str = """
    @Override
    public String toString() {
        return "[%ClassName%]([%SheetName%]){" +
                [%ToStringContent%]
                '}';
    }
    """
    VO_GET_OTHER_VO: str = """
    /**
     * [%Comment%] (实体)
     * @return
     */
    public [%OthClassName%] get[%OthClassName%]() {
        return getLib().[%OthMethod%]([%KeyName%]);
    }
    """
    CONST_LIB_CONSTANT_STRING: str = """
    /**
     * [%Comment%]
     */
    public static final String [%ConstName%] = "[%ConstValue%]";
    """
    CONST_LIB_CONSTANT_LIST: str = """
    /**
     * [%Comment%] dataFullName Relation: [%DataName%]() -> List<[%ClassName%]>
     */
    public static final String [%ConstName%] = "[%ConstValue%]";
    """
    CONST_LIB_CONSTANT_MAP: str = """
    /**
     * [%Comment%] dataFullName Relation: [%DataName%]([%KeyType%] [%KeyName%]) -> [%ClassName%]
     */
    public static final String [%ConstName%] = "[%ConstValue%]";
    """
    CONST_LIB_CONSTANT_MAP2LIST: str = """
    /**
     * [%Comment%] dataFullName Relation: [%DataName%]([%KeyType%] [%KeyName%]) -> List<[%ClassName%]>
     */
    public static final String [%ConstName%] = "[%ConstValue%]";
    """
    CONST_LIB_CONSTANT_MULT_KEY_MAP: str = """
    /**
     * [%Comment%] dataFullName Relation: [%DataName%]([%MultKeysAndTypes%]) -> [%ClassName%]
     */
    public static final String [%ConstName%] = "[%ConstValue%]";
    """
    LOAD_METHOD_LIST: str = """
    public List<[%ClassName%]> [%DataName%]() {
        return (List<[%ClassName%]>) maps.get("[%DataFullName%]");
    }"""
    LOAD_METHOD_MAP: str = """
    public [%ClassName%] [%DataName%]([%KeyType%] [%KeyName%]) {
        Map<[%KeyTypeWapper%], [%ClassName%]> data = (Map<[%KeyTypeWapper%], [%ClassName%]>) maps.get("[%DataFullName%]");
        return data == null ? null : data.get([%KeyName%]);
    }"""
    LOAD_METHOD_MAP2LIST: str = """
    public List<[%ClassName%]> [%DataName%]([%KeyType%] [%KeyName%]){
        Map<[%KeyTypeWapper%], List<[%ClassName%]>> data = (Map<[%KeyTypeWapper%], List<[%ClassName%]>>) maps.get("[%DataFullName%]");
        return data == null ? null : data.get([%KeyName%]);
    }"""
    LOAD_METHOD_MULT_KEY_MAP: str = """
    public [%ClassName%] [%DataName%]([%MultKeysAndTypes%]) {
        Map<String, [%ClassName%]> data = (Map<String, [%ClassName%]>) maps.get("[%DataFullName%]");
        return data == null ? null : data.get([%QueryMultKeys%]);
    }"""

    voTemplate: str = ""
    """启动时加载进内存的Vo代码模板"""
    libConstTemplate: str = ""
    """启动时加载进内存的常量表代码模板"""
    easyAccessTemplate: str = ""
    """启动时加载进内存的快捷读取类代码模板"""
    voGenerateCodePath: str = ""
    """Vo类生成目录"""

    def getFieldType(self, fieldType: FieldType):
        """
        获取java字段类型文本
        :param fieldType: FieldType
        :return: getFieldType(FieldType) --> str 对应代码的类型字符串
        """
        if fieldType is FieldType.Integer:
            return "int"
        elif fieldType is FieldType.String:
            return "String"
        elif fieldType is FieldType.Boolean:
            return "boolean"
        elif fieldType is FieldType.Float:
            return "float"
        elif fieldType is FieldType.Long:
            return "long"
        elif fieldType is FieldType.Double:
            return "double"

    def getFieldTypeWrapper(self, fieldType: FieldType):
        """
        获取java字段类型文本
        :param fieldType: FieldType
        :return: getFieldType(FieldType) --> str 对应代码的类型字符串
        """
        if fieldType is FieldType.Integer:
            return "Integer"
        elif fieldType is FieldType.String:
            return "String"
        elif fieldType is FieldType.Boolean:
            return "Boolean"
        elif fieldType is FieldType.Float:
            return "Float"
        elif fieldType is FieldType.Long:
            return "Long"
        elif fieldType is FieldType.Double:
            return "Double"

    @property
    def loadTemplate(self):
        """
        加载代码模板
        :return: loadTemplate() --> bool 是否成功加载
        """
        log: Logger = Logger.Logger()
        edata: EData = EData()
        voPath = edata.setting.defDirJavaFile
        if not voPath.endswith("\\"):
            voPath += "\\"
        self.voGenerateCodePath = voPath
        if os.path.isdir(self.voGenerateCodePath) == False:
            log.logError("导出" + self.CODE_NAME + "的VO目录不存在：" + voPath)
            return False
        # 读文件加载Vo模板
        if not os.path.isfile(self.TEMPLATE_VO_FILEPATH):
            log.logError(self.CODE_NAME + "Vo模板文件打开失败，请检查文件是否存在：" + self.TEMPLATE_VO_FILEPATH)
            return False
        try:
            f = open(self.TEMPLATE_VO_FILEPATH, "r", encoding='utf8')
            self.voTemplate = f.read()
        except UnicodeDecodeError as e:
            log.logError(self.CODE_NAME + "Vo模板文件打开失败，文件必须为Utf-8格式：" + self.TEMPLATE_VO_FILEPATH)
            return False
        finally:
            f.close()
        # 读文件加载Loader模板
        if not os.path.isfile(self.TEMPLATE_EASY_ACCESS_FILEPATH):
            log.logError(self.CODE_NAME + "Loader模板文件打开失败，请检查文件是否存在：" + self.TEMPLATE_EASY_ACCESS_FILEPATH)
            return False
        try:
            f = open(self.TEMPLATE_EASY_ACCESS_FILEPATH, "r", encoding='utf8')
            self.easyAccessTemplate = f.read()
        except UnicodeDecodeError as e:
            log.logError(self.CODE_NAME + "Loader模板文件打开失败，文件必须为Utf-8格式：" + self.TEMPLATE_EASY_ACCESS_FILEPATH)
            return False
        finally:
            f.close()
        # 读文件加载常量表模板
        if not os.path.isfile(self.TEMPLATE_LIB_CONSTANT):
            log.logError(self.CODE_NAME + "Loader模板文件打开失败，请检查文件是否存在：" + self.TEMPLATE_LIB_CONSTANT)
            return False
        try:
            f = open(self.TEMPLATE_LIB_CONSTANT, "r", encoding='utf8')
            self.libConstTemplate = f.read()
        except UnicodeDecodeError as e:
            log.logError(self.CODE_NAME + "Loader模板文件打开失败，文件必须为Utf-8格式：" + self.TEMPLATE_LIB_CONSTANT)
            return False
        finally:
            f.close()

        return True

    def getterName(self, field: FieldData):
        s: str = field.fieldKey
        if field.fieldType is FieldType.Boolean:
            if s.lower().startswith("is"):
                sf = s[:len(s) - 2]
                ss = s[2:].lower()
                s = sf + ss
            else:
                ss = list(s)
                ss[0] = ss[0].upper()
                s = "is" + "".join(ss)
        else:
            ss = list(s)
            ss[0] = ss[0].upper()
            s = "get" + "".join(ss)
        return s

    def setterName(self, field: FieldData):
        s: str = field.fieldKey
        if field.fieldType is FieldType.Boolean:
            if s.lower().startswith("is"):
                s = s[2:]
        ss = list(s)
        ss[0] = ss[0].upper()
        s = "set" + "".join(ss)
        return s

    def generateCode(self, exportVersion: str, exportTime: datetime):
        """
        生成代码 在执行前，必须先执行loadTemplate()
        :param exportVersion: str 发布版本号
        :param exportTime: datetime 发布日期
        :return: bool 是否顺利
        """
        edata: EData = EData()
        log: Logger = Logger.Logger()

        # 删除之前生成的.java文件
        filelist = os.listdir(self.voGenerateCodePath)
        for filename in filelist:
            filepath = self.voGenerateCodePath + filename
            if os.path.isfile(filepath):
                if filename.endswith(self.CODE_FILE_EXT_NAME):
                    os.remove(filepath)
                    print("删除旧文件：" + filepath)

        # 开始生成实体类
        for excel in edata.excelDatas.values():
            for table in excel.tables.values():
                # 检查是否已勾选导出
                if not table.isListChecked:
                    continue
                fieldDefineCode = ""
                fieldGetSetCode = ""
                methodToGetOtherVoCode = ""
                toStringContentCode = ""
                for field in table.fields:
                    # 从模板替换创建字段定义
                    fieldDefineCode += self.VO_FIELD_COMMENT.replace("[%Comment%]", field.fieldName)
                    fieldDefineCode += "\n"
                    tmpField: str = self.VO_FIELD_DEFINE
                    tmpField = tmpField.replace("[%FieldType%]", self.getFieldType(field.fieldType))
                    tmpField = tmpField.replace("[%FieldName%]", field.fieldKey)
                    fieldDefineCode += tmpField
                    fieldDefineCode += "\n"
                    # 从模板替换创建Getter
                    fieldGetSetCode += self.VO_FIELD_COMMENT.replace("[%Comment%]", field.fieldName)
                    fieldGetSetCode += "\n"
                    tmpGetter: str = self.VO_FIELD_GET
                    tmpGetter = tmpGetter.replace("[%FieldType%]", self.getFieldType(field.fieldType))
                    tmpGetter = tmpGetter.replace("[%FieldName%]", field.fieldKey)
                    tmpGetter = tmpGetter.replace("[%Get%]", self.getterName(field))
                    fieldGetSetCode += tmpGetter
                    fieldGetSetCode += "\n"
                    # 从模板替换创建Setter
                    tmpSetter: str = self.VO_FIELD_SET
                    tmpSetter = tmpSetter.replace("[%FieldType%]", self.getFieldType(field.fieldType))
                    tmpSetter = tmpSetter.replace("[%FieldName%]", field.fieldKey)
                    tmpSetter = tmpSetter.replace("[%Set%]", self.setterName(field))
                    fieldGetSetCode += tmpSetter
                    fieldGetSetCode += "\n"
                    # 生成ToString代码
                    if toStringContentCode != "":
                        toStringContentCode += "                \", "
                    else:
                        toStringContentCode += "\""
                    if field.fieldType == FieldType.String:
                        toStringContentCode += field.fieldKey + "='\" + " + field.fieldKey + " + '\\'' + \n"
                    else:
                        toStringContentCode += field.fieldKey + "=\" + " + field.fieldKey + " + \n"

                # 获取“字段关联”功能(其他类实体快捷调用)的代码
                for myCfg in table.loadConfigs:
                    for union in myCfg.unions:
                        # 有配置关联才会进来循环
                        uTable: TableData = None
                        # 尝试找出对应的关联的表
                        for uExcel in edata.excelDatas.values():
                            for utable2 in uExcel.tables.values():
                                if union.unionTableName == utable2.sheetName:
                                    uTable = utable2
                        if uTable is None:
                            log.logError("严重错误！ [" + table.sheetName + "]配置表'字段关联'中找不到关联字段的表：" + union.unionTableName)
                            return False

                        # 尝试找出对应的关联的表里面的键值
                        # uField: FieldData = None
                        # for uField2 in uTable.fields:
                        #     if union.unionKey == uField2.fieldKey:
                        #         uField = uField2
                        # if uField is None:
                        #     log.logError("严重错误！ [" + table.sheetName + "]配置表'字段关联'中找不到关联字段的键：" + union.unionKey)
                        #     return False
                        # if not uField.isMainKey:
                        #     log.logError("严重错误！ [" + table.sheetName + "]配置表'字段关联'中找不到关联字段不是该表的主键：" + union.unionTableName + "->" + union.unionKey)
                        #     return False

                        # 检查目标表下有没有做对应绑定字段索引的方式
                        uCfg: LoadConfig = None
                        for uCfg2 in uTable.loadConfigs:
                            if uCfg2.type == IndexType.List or uCfg2.type == IndexType.MultKeyMap:
                                continue
                            if uCfg2.keyFields[0].fieldKey == union.unionKey:
                                uCfg = uCfg2
                        if uCfg is None:
                            log.logError(
                                "严重错误！ [" + table.sheetName + "]配置表[" + union.unionTableName + "]'字段关联'中找不到关联字段的键：" + union.unionKey)
                            return False
                        # 检查绑定自身字段是否存在
                        myField: FieldData = None
                        for myField2 in table.fields:
                            if myField2.fieldKey == union.unionKey:
                                myField = myField2
                        if myField is None:
                            log.logError("严重错误！ [" + table.sheetName + "]配置表'字段关联'中, 本表没有该字段：" + union.myKey)
                            return False
                        if uCfg.keyFields[0].fieldType != myField.fieldType:
                            log.logError(
                                "严重错误！ [" + table.sheetName + "]配置表'字段关联'中, 本表字段：" + union.myKey + "与目标字段类型不一致!")
                            return False
                        # 全部检验完毕，开始生产绑定数据
                        tmpOther: str = self.VO_GET_OTHER_VO
                        tmpOther = tmpOther.replace("[%Comment%]",
                                                    uTable.sheetName + "  关联：" + union.myKey + "->" + uTable.getClassname() + "." + union.unionKey)
                        tmpOther = tmpOther.replace("[%OthSheetName%]", "对应资料Vo")
                        tmpOther = tmpOther.replace("[%OthClassName%]", uTable.getClassname())
                        tmpOther = tmpOther.replace("[%OthMethod%]", uCfg.getDataName(uTable))
                        tmpOther = tmpOther.replace("[%KeyName%]", myField.fieldKey)
                        methodToGetOtherVoCode += tmpOther

                # 提取类名
                className: str = table.getClassname()
                # 文件名
                filename: str = className + self.CODE_FILE_EXT_NAME
                # toString生成模板
                toStringCode: str = self.VO_TOSTRING
                toStringCode = toStringCode.replace("[%ClassName%]", className)
                toStringCode = toStringCode.replace("[%SheetName%]", table.sheetName)
                toStringCode = toStringCode.replace("[%ToStringContent%]", toStringContentCode.strip("\n"))
                # 类实体生成
                classConent: str = fieldDefineCode
                classConent += "\n\n"
                classConent += fieldGetSetCode
                classConent += methodToGetOtherVoCode
                classConent += toStringCode
                # 类模板替换
                voCode: str = self.voTemplate
                voCode = voCode.replace("[%Comment%]", table.sheetName)
                voCode = voCode.replace("[%Author%]", "Generate By Excel2Json")
                voCode = voCode.replace("[%Datetime%]", Util.getDateTimeString(exportTime))
                voCode = voCode.replace("[%Version%]", exportVersion)
                voCode = voCode.replace("[%ClassName%]", className)
                voCode = voCode.replace("[%Content%]", classConent)
                # 保存文件
                filepath = self.voGenerateCodePath + filename
                f = open(filepath, 'w', encoding='utf8')
                f.write(voCode)
                f.close()
                log.logSuccess("生成Java实体类：" + filename)

        # 开始生成实体类
        constantGroupDict: dict = dict()
        loaderContent: str = ""
        fieldConstContent: str = ""
        for excel in edata.excelDatas.values():
            for table in excel.tables.values():
                # 检查是否已勾选导出
                if not table.isListChecked:
                    continue

                for cfg in table.loadConfigs:
                    # 根据loadConfig配置生成数据表
                    code: str = ""
                    fieldCode: str = ""
                    multKeysAndTypes: str = ""
                    multKeysQuery: str = ""
                    if cfg.type is IndexType.List:
                        code = self.LOAD_METHOD_LIST
                        fieldCode = self.CONST_LIB_CONSTANT_LIST
                    elif cfg.type is IndexType.Map:
                        code = self.LOAD_METHOD_MAP
                        fieldCode = self.CONST_LIB_CONSTANT_MAP
                    elif cfg.type is IndexType.Map2List:
                        code = self.LOAD_METHOD_MAP2LIST
                        fieldCode = self.CONST_LIB_CONSTANT_MAP2LIST
                    elif cfg.type is IndexType.MultKeyMap:
                        code = self.LOAD_METHOD_MULT_KEY_MAP
                        fieldCode = self.CONST_LIB_CONSTANT_MULT_KEY_MAP
                        # 计算多个参数的MultKeyType
                        for field in cfg.keyFields:
                            multKeysAndTypes += self.getFieldType(field.fieldType) + " "
                            multKeysAndTypes += field.fieldKey + ", "
                            multKeysQuery += field.fieldKey + " + \"_\" + "
                        if len(multKeysAndTypes) > 2:
                            # 减掉最后的', '
                            multKeysAndTypes = multKeysAndTypes[:-2]
                        if len(multKeysQuery) > 3:
                            # 减掉最后的', '
                            multKeysQuery = multKeysQuery[:-3]

                    code = code.replace("[%ClassName%]", table.getClassname())
                    code = code.replace("[%DataName%]", cfg.getDataName(table))
                    code = code.replace("[%DataFullName%]", cfg.getDataFullName(table))
                    # code中用的基础类型(如int)
                    code = code.replace("[%KeyType%]", self.getFieldType(cfg.keyFields[0].fieldType))
                    # code中用的装饰类包装的基础类型（如Integer）
                    code = code.replace("[%KeyTypeWapper%]", self.getFieldTypeWrapper(cfg.keyFields[0].fieldType))
                    code = code.replace("[%KeyName%]", cfg.keyFields[0].fieldKey)
                    code = code.replace("[%MultKeysAndTypes%]", multKeysAndTypes)
                    code = code.replace("[%QueryMultKeys%]", multKeysQuery)
                    loaderContent += code

                    # 把出现的group都记录下来
                    if cfg.group != "":
                        subGroupList: List
                        subGroupList = constantGroupDict.get(cfg.group)
                        if subGroupList is None:
                            subGroupList = []
                            constantGroupDict[cfg.group] = subGroupList
                        subGroupList.append([cfg.getDataFullName(table), table.getClassname(), table.sheetName])

                    # 常量表字段模板替换
                    fieldCode = fieldCode.replace("[%ClassName%]", table.getClassname())
                    fieldCode = fieldCode.replace("[%DataName%]", cfg.getDataName(table))
                    fieldCode = fieldCode.replace("[%KeyType%]", self.getFieldType(cfg.keyFields[0].fieldType))
                    fieldCode = fieldCode.replace("[%KeyName%]", cfg.keyFields[0].fieldKey)
                    fieldCode = fieldCode.replace("[%MultKeysAndTypes%]", multKeysAndTypes)
                    constName: str = edata.setting.libTablePrefix + cfg.getConstantDataFullName(table)
                    constName = constName[:-1]
                    fieldCode = fieldCode.replace("[%ConstName%]", constName)
                    fieldCode = fieldCode.replace("[%ConstValue%]", cfg.getDataFullName(table))
                    comment: str = "表[" + table.sheetName + ":" + table.jsonName + "] "
                    fieldCode = fieldCode.replace("[%Comment%]", comment)
                    fieldConstContent += fieldCode


        # 简易读取类模板替换
        loaderCode: str = self.easyAccessTemplate
        easyAccessClassName: str = edata.setting.genLibEasyAccessClass
        easyAccessFileName: str = edata.setting.genLibEasyAccessClass + ".java"
        loaderCode = loaderCode.replace("[%Author%]", "Generate By Excel2Json")
        loaderCode = loaderCode.replace("[%Datetime%]", Util.getDateTimeString(exportTime))
        loaderCode = loaderCode.replace("[%Version%]", exportVersion)
        loaderCode = loaderCode.replace("[%ClassName%]", easyAccessClassName)
        loaderCode = loaderCode.replace("[%Content%]", loaderContent)
        # 保存简易读取类文件
        filepath = self.voGenerateCodePath + easyAccessFileName
        f = open(filepath, 'w', encoding='utf8')
        f.write(loaderCode)
        f.close()
        log.logSuccess("生成Java快捷加载类：" + easyAccessFileName)

        # 提取Group信息
        constantContentCode: str = ""
        for gKey,gVal in constantGroupDict.items():
            groupKey: str = gKey
            subGroupList: List = gVal
            code: str = self.CONST_LIB_CONSTANT_STRING
            code = code.replace("[%ConstName%]", (edata.setting.groupPrefix + groupKey).upper())
            code = code.replace("[%ConstValue%]", edata.setting.groupPrefix + groupKey)
            comment: str = "组别[" + groupKey + "] 包含表如下："
            for gdata in subGroupList:
                dataFullName = gdata[0]
                classname = gdata[1]
                sheetName = gdata[2]
                comment += "<BR/>[" + sheetName + " " + classname + "] -> " + dataFullName + "();"
            code = code.replace("[%Comment%]", comment)
            constantContentCode += code

        # for excel in edata.excelDatas.values():
        #     for table in excel.tables.values():
        #         # 检查是否已勾选导出
        #         if not table.isListChecked:
        #             continue
        #         for cfg in table.loadConfigs:
        #             code: str = self.CONST_LIB_CONSTANT_STRING
        #             code = code.replace("[%ConstName%]", edata.setting.libTablePrefix + cfg.getConstantDataFullName(table))
        #             code = code.replace("[%ConstValue%]", cfg.getDataFullName(table))
        #             comment: str = "表[" + table.sheetName + ":" + table.jsonName + "] ->" + cfg.getDataFullName(table) + "</BR>"
        #             comment += "@return " + table.getClassname()
        #             cfg.getDataFullName(table)
        #             code = code.replace("[%Comment%]", comment)
        #             constantContentCode += code
        constantContentCode += fieldConstContent

        # 常量表模板替换
        constCode: str = self.libConstTemplate
        libConstantClassName: str = edata.setting.genLibConstantClass
        libConstantFileName: str = edata.setting.genLibConstantClass + ".java"
        constCode = constCode.replace("[%Author%]", "Generate By Excel2Json")
        constCode = constCode.replace("[%Datetime%]", Util.getDateTimeString(exportTime))
        constCode = constCode.replace("[%Version%]", exportVersion)
        constCode = constCode.replace("[%ClassName%]", libConstantClassName)
        constCode = constCode.replace("[%Content%]", constantContentCode)

        # 保存常量表文件
        filepath = self.voGenerateCodePath + libConstantFileName
        f = open(filepath, 'w', encoding='utf8')
        f.write(constCode)
        f.close()
        log.logSuccess("生成Java快捷加载类：" + libConstantFileName)


        return True
